package binder

import "mi/compiler/ast"

type FlowFlags int
const (
	FFReferenced            FlowFlags = 1 << iota           // Referenced as antecedent once
	FFShared                // Referenced as antecedent more than once
)

type FlowNode interface {
	ID() int
	SetID(id int)
	Flags() FlowFlags
	setFlags(flags FlowFlags)
	andFlags(flags FlowFlags)
	orFlags(flags FlowFlags)
}

type flowNode struct {
	flags FlowFlags
	Id    int // Node id used by flow type cache in checker
}

func (f *flowNode) ID () int { return f.Id }
func (f *flowNode) SetID (id int) { f.Id = id }
func (f *flowNode) Flags () FlowFlags { return f.flags }
func (f *flowNode) setFlags (flags FlowFlags) { f.flags = flags }
func (f *flowNode) andFlags (flags FlowFlags) { f.flags |= flags }
func (f *flowNode) orFlags (flags FlowFlags) { flags &= flags }

type UnreachableFlow struct {
	flowNode
}

type FlowLock interface {
	FlowNode
	Lock()
	UnLock()
	Locked() bool
}

type flowLock struct {
	locked bool
	flowNode
}

func (f *flowLock) Lock() { f.locked = true }
func (f *flowLock) UnLock() { f.locked = false }
func (f *flowLock) Locked() bool { return f.locked }

type AfterFinallyFlow struct {
	Antecedent FlowNode
	flowLock
}
func (a AfterFinallyFlow) aLock() {}

type PreFinallyFlow struct {
	Antecedent FlowNode
	Lock       FlowLock
	flowNode
}

// FlowStart represents the start of a control flow. For a function expression or arrow
// function, the container property references the function (which in turn has a flowNode
// property for the containing control flow).
type FlowStart struct {
	Container ast.Node
	flowNode
}

type LabelKind int
const (
	BranchLabel LabelKind = iota         // Non-looping junction
	LoopLabel                            // Looping junction
)

// FlowLabel represents a junction with multiple possible preceding control flows.
type FlowLabel struct {
	Kind LabelKind
	Antecedents []FlowNode
	flowNode
}

// FlowAssignment represents a ast that assigns a value to a narrowable reference,
// i.e. an identifier or a dotted name that starts with an identifier or 'this'.
type FlowAssignment struct {
	Node       ast.Node
	Antecedent FlowNode
	flowNode
}

// FlowCondition represents a condition that is known to be true or false at the
// ast's location in the control flow.
type FlowCondition struct {
	Value      bool
	Expr       ast.Expression
	Antecedent FlowNode
	flowNode
}

type FlowSwitchClause struct {
	SwitchStatement *ast.SwitchStatement
	ClauseStart     int // Start index of case/default clause range
	ClauseEnd       int // End index of case/default clause range
	Antecedent      FlowNode
	flowNode
}

// FlowArrayMutation represents a ast potentially mutates an array, i.e. an
// operation of the form 'x.push(value)', 'x.unshift(value)' or 'x[n] = value'.
type FlowArrayMutation struct {
	Node       ast.Expression
	Antecedent FlowNode
	flowNode
}

func IsUnreachableFlow(f FlowNode) bool {
	_, ok := f.(*UnreachableFlow)
	return ok
}

func IsFlowLock(f FlowNode) bool {
	_, ok := f.(FlowLock)
	return ok
}

func IsAfterFinallyFlow(f FlowNode) bool {
	_, ok := f.(*AfterFinallyFlow)
	return ok
}

func IsPreFinallyFlow(f FlowNode) bool {
	_, ok := f.(*PreFinallyFlow)
	return ok
}

func IsFlowStart(f FlowNode) bool {
	_, ok := f.(*FlowStart)
	return ok
}

func IsFlowLabel(f FlowNode) bool {
	_, ok := f.(*FlowLabel)
	return ok
}

func IsFlowAssign(f FlowNode) bool {
	_, ok := f.(*FlowAssignment)
	return ok
}

func IsFlowCond(f FlowNode) bool {
	_, ok := f.(*FlowCondition)
	return ok
}

func IsFlowSwitchClause(f FlowNode) bool {
	_, ok := f.(*FlowSwitchClause)
	return ok
}

func IsFlowArrayMutation(f FlowNode) bool {
	_, ok := f.(*FlowArrayMutation)
	return ok
}
